package com.zy.explore.client.infrastructure.plugins.util;

import com.zy.explore.client.infrastructure.util.PropertiesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.lang.reflect.InvocationTargetException;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * <p>建类说明：sql打印工具类</p>
 *
 * @author 中国印
 * @date 2022年02月17日 17:03:06
 */
@Slf4j
public class SqlPrintUtil {

    private static final List<String> PRINT_SQL_ENV_LIST = Arrays.asList("local", "dev", "druid");

    /**
     * <p>属性说明：sql执行时间阈值，单位毫秒，生产环境超过这个时间则打印慢日志</p>
     */
    private static final int timeThreshold = 2000;

    private SqlPrintUtil() {}

    /**
     * 方法说明：打印查询sql
     *
     * @param invocation 调用
     * @param ms         MappedStatement对象
     * @param boundSql   绑定sql
     * @throws InvocationTargetException 调用目标异常
     * @throws IllegalAccessException    非法访问异常
     */
    public static Object printQuerySql(Invocation invocation, MappedStatement ms, BoundSql boundSql) throws InvocationTargetException, IllegalAccessException {
        String sqlId = ms.getId();
        // System.out.println("完整SQL: " + boundSql.getSql().replaceAll("[\\s]+", " "));
        long start = System.currentTimeMillis();
        Object returnValue = invocation.proceed();
        long end = System.currentTimeMillis();
        long time = (end - start);
        Configuration configuration = ms.getConfiguration();
        // 这里可以控制打印耗时sql (生产环境耗时超过2秒就打印sql，开发环境总是打印sql)
        List<String> configEnvList = Arrays.asList(PropertiesUtil.getProperty("spring.profiles.active", "prod").split(","));
        // 两个集合交集：CollectionUtils.intersection(list1, list2)
        if (time >= timeThreshold || CollectionUtils.intersection(PRINT_SQL_ENV_LIST, configEnvList).size() > 0) {
            String sql = SqlPrintUtil.getFormatSql(configuration, boundSql, sqlId, time);
            // 控制台打印sql
            System.err.println(sql);
            // 日志记录sql
            log.error(sql);
        }
        return returnValue;
    }

    /**
     * <p>方法说明：打印非查询sql，即打印增删改sql</p>
     *
     * @param invocation 调用
     * @return {@link Object }
     * @throws InvocationTargetException 调用目标异常
     * @throws IllegalAccessException    非法访问异常
     */
    public static Object printNonQuerySql(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
        final Object[] queryArgs = invocation.getArgs();
        if (queryArgs.length <= 0 || !(queryArgs[0] instanceof MappedStatement)) {
            return invocation.proceed();
        }
        final MappedStatement mappedStatement = (MappedStatement) queryArgs[0];
        final Object parameter = queryArgs[1];

        BoundSql boundSql = mappedStatement.getBoundSql(parameter);

        return printQuerySql(invocation, mappedStatement, boundSql);
    }

    public static String getFormatSql(Configuration configuration, BoundSql boundSql, String sqlId, long time) {
        String sql = fillSqlParams(configuration, boundSql);
        return "【SQL_ID】=>" + sqlId + "; 【耗时】=>" + time + "ms; 【SQL】=>" + sql + ";";
    }

    public static String fillSqlParams(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (parameterMappings.size() > 0 && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    }
                }
            }
        }
        return sql;
    }

    private static String getParameterValue(Object obj) {
        String value;
        if (obj instanceof String) {
            value = "'" + obj + "'";
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format(new Date()) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }
        }
        return value;
    }
}
